#pragma once
#include "BaseDefine.h"
#include "BoundingSphere.h"

namespace WorldWind
{
	/// <summary>
	/// The region of space in the modeled world that may appear on the screen; it is the field of view of the notional camera.
	/// Used to perform culling of invisible object (prior to rendering) to increase speed.
	/// See: http://en.wikipedia.org/wiki/Viewing_frustum
	/// </summary>
	class Frustum
	{
	public:
#define PLANE_NUM 6
		D3DXPLANE planes[PLANE_NUM];
		Frustum()
		{
			memset(planes,0,sizeof(planes));
		}
		void Update(D3DXMATRIX m)
		{
			//bottom (down) plane
			planes[0].a = m._14+m._12;//a
			planes[0].b = m._24 + m._22; //b
			planes[0].c = m._34 + m._32; //c
			planes[0].d = m._44 + m._42; //d
			
			//far plane
			planes[1].a = 	m._14 - m._13;
			planes[1].b = 	m._24 - m._23;
			planes[1].c = 	m._34 - m._33;
			planes[1].d = 	m._44 - m._43;

			//right side plane
			planes[2].a =	m._14 - m._11; //a
			planes[2].b =	m._24 - m._21; //b
			planes[2].c =	m._34 - m._31; //c
			planes[2].d =	m._44 - m._41; //d

			//left side plane
			planes[3].a =	m._14 + m._11;	//a
			planes[3].b =	m._24 + m._21;	//b
			planes[3].c =	m._34 + m._31;	//c
			planes[3].d =	m._44 + m._41;	//d

			//near plane
			planes[4].a =	m._13;
			planes[4].b =	m._23;
			planes[4].c =	m._33;
			planes[4].d =	m._43;

			//top (up) plane
			planes[5].a =	m._14 - m._12; //a
			planes[5].b =	m._24 - m._22; //b
			planes[5].c =	m._34 - m._32; //c
			planes[5].d =	m._44 - m._42;//d

			D3DXPLANE temp;
			for (int i=0;i<PLANE_NUM;i++)
			{
				planes[i] = *D3DXPlaneNormalize(&temp,&planes[i]);
			}
		}

		/// <summary>
		/// Test if a sphere intersects or is completely inside the frustum.
		/// </summary>
		/// <returns>true when the sphere intersects.</returns>
		bool Intersects(BoundingSphere c)
		{
			for (int i=0;i<PLANE_NUM;i++)
			{
				float distancePlaneToPoint = planes[i].a * c.Center.x + planes[i].b * c.Center.y + planes[i].c * c.Center.z + planes[i].d;
				if(distancePlaneToPoint < -c.Radius)
					// More than 1 radius outside the plane = outside
					return false;
			}

			//else it's in view
			return true;
		}

		/// <summary>
		/// Test if a point is inside the frustum.
		/// </summary>
		/// <returns>true when the point is inside.</returns>
		/// <param name="v">XYZ in world coordinates of the point to test.</param>
		bool ContainsPoint(D3DXVECTOR3 v)
		{
			for (int i=0;i<PLANE_NUM;i++)
			{
				D3DXVECTOR3 v1(planes[i].a, planes[i].b, planes[i].c);
				if(D3DXVec3Dot(&v1,v)+ planes[i].D <0)
					return false;
			}
			return true;
		}

		/// <summary>
		/// Tests if the view frustum fully contains the bounding box.
		/// </summary>
		/// <returns>true when the box is complete enclosed by the frustum.</returns>
		bool Contains(BoundingBox bb)
		{
			//Code taken from Flip Code Article:
			// http://www.flipcode.com/articles/article_frustumculling.shtml
			int iTotalIn = 0;
			foreach(Plane p in this.planes)
			{
				int iInCount = 8;
				int iPtIn = 1;
				// TODO: Modify bounding box and only check 2 corners.
				for(int i = 0; i < 8; i++)
				{
					if(Vector3.Dot(new Vector3(p.A,p.B,p.C), bb.corners[i]) + p.D < 0)
					{
						iPtIn = 0;
						--iInCount;
					}
				}

				if(iInCount == 0)
					return false;

				iTotalIn += iPtIn;
			}

			if(iTotalIn == 6)
				return true;

			return false;
		}

		/// <summary>
		/// Tests if the bounding box specified intersects with or is fully contained in the frustum.
		/// </summary>
		/// <returns>true when the box intersects with the frustum.</returns>
		bool Intersects(BoundingBox bb)
		{
			foreach(Plane p in this.planes)
			{
				Vector3 v = new Vector3(p.A,p.B,p.C);
				bool isInside = false;
				// TODO: Modify bounding box and only check 2 corners.
				for(int i = 0; i < 8; i++)
				{
					if(Vector3.Dot(v, bb.corners[i]) + p.D >= 0)
					{
						isInside = true;
						break;
					}
				}

				if(!isInside)
					return false;
			}

			return true;
		}

		CString ToString()
		{
			CString res;
			res.Format(_T("Near:\n%d Far:\n%d"),planes[4], planes[1]);
			return res;
		}
	}
}
